﻿#Requires AutoHotkey v2.0

;;
;; An autohotkey script that provides emacs-like keybinding on Windows
;; 在Windows11上使用AutoHotKey实现Emacs快捷键
;; https://emacs-china.org/t/windows-emacs/10994
;;

#UseHook

; The following line is a contribution of NTEmacs wiki http://www49.atwiki.jp/ntemacs/pages/20.html
SetKeyDelay 0

; turns to be 1 when ctrl-x is pressed
global is_pre_x := 0
; turns to be 1 when ctrl-space is pressed
global is_pre_spc := 0

global is_pre_c := 0

global is_pre_win_c := 0
; Applications you want to disable emacs-like keybindings
; (Please comment out applications you don't use)

is_wsl2_emacs() {
    ; 检测当前活动窗口的标题或类名
    ActiveTitle := WinGetTitle("A")
    ActiveClass := WinGetClass("A")

    ; 如果窗口标题或类名包含 "Emacs"，跳过操作
    ; 通过测试，wsl打开的emacs窗口名称为(Ubuntu-24.04)
    if (InStr(ActiveTitle, "(Ubuntu-24.04)") || InStr(ActiveClass, "Emacs")) {
        return 1
    }
}

is_need_ignore() {
    ;; 1是忽略该程序
    if is_wsl2_emacs()
        return 1

    if WinActive("ahk_exe Explorer.exe")
        return 0
    if WinActive("ahk_exe WindowsTerminal.exe")
        return 1
    if WinActive("ahk_class ConsoleWindowClass") ; Cygwin
        return 1
    if WinActive("ahk_class MEADOW") ; Meadow
        return 1
    if WinActive("ahk_class cygwin/x X rl-xterm-XTerm-0")
        return 1
    if WinActive("ahk_class MozillaUIWindowClass") ; keysnail on Firefox
        return 1
    ; Avoid VMwareUnity with AutoHotkey
    if WinActive("ahk_class VMwareUnityHostWndClass")
        return 1
    if WinActive("ahk_class Vim") ; GVIM
        return 1
    if WinActive("ahk_exe Code.exe")
        return 0
    if WinActive("ahk_class mintty")
        return 1
    return 0
}

delete_char() {
    Send "{Del}"
    global is_pre_spc := 0
    return
}
delete_backward_char() {
    Send "{BS}"
    global is_pre_spc := 0
    return
}
kill_line() {
    Send "{ShiftDown}{END}{ShiftUp}"
    Sleep 50 ;[ms] this value depends on your environment
    Send "^x"
    global is_pre_spc := 0
    return
}
open_line() {
    Send "{HOME}{Enter}{Up}"
    global is_pre_spc := 0
    return
}
quit() {
    Send "{Esc}"
    global is_pre_spc := 0
    return
}
newline() {
    Send "{Enter}"
    global is_pre_spc := 0
    return
}
indent_for_tab_command() {
    Send "{Tab}"
    global is_pre_spc := 0
    return
}
newline_and_indent() {
    Send "{Enter}{Tab}"
    global is_pre_spc := 0
    return
}
isearch_forward() {
    Send "^f"
    global is_pre_spc := 0
    return
}
isearch_backward() {
    Send "^f"
    global is_pre_spc := 0
    return
}
kill_region() {
    Send "^x"
    global is_pre_spc := 0
    return
}
kill_ring_save() {
    global
    Send "^c"
    if is_pre_spc {
        Send "{Esc}"
    }
    is_pre_spc := 0
    return
}
yank() {
    Send "^v"
    global is_pre_spc := 0
    return
}
undo() {
    Send "^z"
    global is_pre_spc := 0
    return
}
find_file() {
    Send "^o"
    global is_pre_x := 0
    return
}
save_buffer() {
    Send "^s"
    global is_pre_x := 0
    return
}
kill_emacs() {
    Send "!{F4}"
    global is_pre_x := 0
    return
}

move_beginning_of_line() {
    global
    if is_pre_spc
        Send "+{HOME}"
    else
        Send "{HOME}"
    return
}
move_end_of_line() {
    global
    if is_pre_spc
        Send "+{END}"
    else
        Send "{END}"
    return
}
previous_line() {
    global
    if is_pre_spc
        Send "+{Up}"
    else
        Send "{Up}"
    return
}
next_line() {
    global
    if is_pre_spc
        Send "+{Down}"
    else
        Send "{Down}"
    return
}
forward_char() {
    global
    if is_pre_spc
        Send "+{Right}"
    else
        Send "{Right}"
    return
}
backward_char() {
    global
    if is_pre_spc
        Send "+{Left}"
    else
        Send "{Left}"
    return
}
scroll_up() {
    global
    if is_pre_spc
        Send "+{PgUp}"
    else
        Send "{PgUp}"
    return
}
scroll_down() {
    global
    if is_pre_spc
        Send "+{PgDn}"
    else
        Send "{PgDn}"
    return
}
format_code() {
    global
    ; 获取当前活动窗口的进程名
    ActiveProcess := WinGetProcessName("A")

    ; 依托于各软件的快捷键
    if (ActiveProcess = "Code.exe") {
        Send "!+f"
    } else if (ActiveProcess = "idea64.exe") {
        Send "^!l"
    }
    is_pre_c := 0
}
run_code() {
    global
    ; 获取当前活动窗口的进程名
    ActiveProcess := WinGetProcessName("A")

    ; 依托于各软件的快捷键
    if (ActiveProcess = "idea64.exe") {
        Send "+{F10}"
    }
    is_pre_c := 0
}

; 以C-x为前缀的快捷键
^x:: {
    global
    if is_need_ignore()
        Send A_ThisHotkey
    else
        is_pre_x := 1
    return
}

; 删除光标后的一个字符
^d:: {
    if is_need_ignore()
        Send A_ThisHotkey
    else
        delete_char()
    return
}

; 删除当前行光标后的所有字符
^k:: {
    if is_need_ignore()
        Send A_ThisHotkey
    else
        kill_line()
    return
}

; 取消当前操作
^g:: {
    global
    if is_need_ignore()
        Send A_ThisHotkey
    else
        quit()
    return
}

^s:: {
    if is_need_ignore()
        Send A_ThisHotkey
    else {
        if is_pre_x
            save_buffer()
        else
            isearch_forward()
    }
    return
}

^w:: {
    if is_need_ignore()
        Send A_ThisHotkey
    else
        kill_region()
    return
}

!w:: {
    if is_need_ignore()
        Send A_ThisHotkey
    else {
        kill_ring_save()
    }
    return
}

^y:: {
    if is_need_ignore()
        Send A_ThisHotkey
    else
        yank()
    return
}

^/:: {
    if is_need_ignore()
        Send A_ThisHotkey
    else
        undo()
    return
}

^a:: {
    if is_need_ignore()
        Send A_ThisHotkey
    else
        move_beginning_of_line()
    return
}

^c:: {
    global
    if is_need_ignore()
        Send A_ThisHotkey
    else
        is_pre_c := 1
    return
}

^e:: {
    if is_need_ignore()
        Send A_ThisHotkey
    else {
        if is_pre_c {
            format_code()
        } else
            move_end_of_line()
    }
    return
}
^p:: {
    if is_need_ignore()
        Send A_ThisHotkey
    else
        previous_line()
    return
}
^n:: {
    if is_need_ignore()
        Send A_ThisHotkey
    else
        next_line()
    return
}
^f:: {
    if is_need_ignore()
        Send A_ThisHotkey
    else {
        if is_pre_x
            find_file()
        else
            forward_char()
    }
    return
}
^b:: {
    if is_need_ignore()
        Send A_ThisHotkey
    else
        backward_char()
    return
}

!+,:: {
    global
    if is_need_ignore()
        Send A_ThisHotkey
    else
        if is_pre_spc {
            Send "+{CtrlDown}+{Home}+{CtrlUp}" ; 选中从光标起始位置到文件首的所有文本
            is_pre_spc := 0
        }
        else
            Send "{CtrlDown}{Home}{CtrlUp}"
    return
}
!+.:: {
    global
    if is_need_ignore()
        Send A_ThisHotkey
    else
        if is_pre_spc {
            Send "+{CtrlDown}+{End}+{CtrlUp}" ; 选中从光标起始位置到文件末尾的所有文本
            is_pre_spc := 0
        }
        else
            Send "{CtrlDown}{End}{CtrlUp}"
    return
}

!a:: {
    if is_need_ignore()
        Send A_ThisHotkey
    else
        Send "^a" ;用send的方式和物理按键好像不一样
    return
}

!Backspace:: {
    if is_need_ignore() {
        if is_wsl2_emacs()
            Send "!{Backspace}" ; 如果不用这种方法，emacs中使用会出错
        else
            Send A_ThisHotkey
    } else
        Send "^{Backspace}" ; 发送 Ctrl+Backspace
    return
}

#w:: {
    if WinActive("ahk_exe idea64.exe")
        Send "^{F4}"
    Send "^w" ; 关闭 Chrome 标签页
    return
}

#q:: {
    Send "!{F4}"
    return
}

!b:: {
    if is_need_ignore()
        Send A_ThisHotkey
    else
        Send "^{Left}" ; 发送 Ctrl+Left
    return
}

!f:: {
    if is_need_ignore()
        Send A_ThisHotkey
    else
        Send "^{Right}" ; 发送 Ctrl+Right
    return
}

!d:: {
    if is_need_ignore()
        Send A_ThisHotkey
    else
        Send "^{Delete}" ; 发送 Ctrl+Del
    return
}

^;:: {
    global
    if is_need_ignore()
        Send A_ThisHotkey
    else {
        if is_pre_x {
            Send "^/"
            is_pre_x := 0
        }
    }
    return
}

; 模拟Emacs中的Ctrl+@
^@:: {
    global
    if is_need_ignore()
        Send A_ThisHotkey
    else {
        if is_pre_spc
            is_pre_spc := 0
        else
            is_pre_spc := 1
    }
    return
}

; 模拟鼠标滚轮滚动到光标所在行居中
ScrollUp() {
    ; 模拟鼠标滚轮向上滚动
    ; SendInput("{WheelUp 5}")  ; 向上滚动 5 次
    ; Sleep(100)  ; 稍作延迟
    SendInput("{WheelDown 5}")  ; 向下滚动 5 次
}

; 模拟鼠标滚轮滚动到光标所在行置顶
ScrollDown() {
    ; 模拟鼠标滚轮向上滚动
    SendInput("{WheelUp 5}")  ; 向上滚动 5 次
}

; 定义 Ctrl+L 为将光标所在行移动到屏幕中间
^l:: {
    global
    if is_need_ignore()
        Send A_ThisHotkey
    else {
        ScrollUp()
    }
    return
}

#v:: {
    global
    Send A_ThisHotkey
    is_pre_win_c := 1
    return
}

; 定义Ctrl+v为向下翻页
^v:: {
    global
    if is_pre_win_c {
        Send A_ThisHotkey
        is_pre_win_c := 0
    } else {
        if is_need_ignore()
            Send A_ThisHotkey
        else
            scroll_down()
    }
    return
}

; 定义Ctrl+v为向上翻页
!v:: {
    if is_need_ignore()
        Send A_ThisHotkey
    else
        scroll_up()
    return
}

; 跳转到指定行
!g:: {
    if is_need_ignore()
        Send A_ThisHotkey
    else
        Send "^g"
    return
}

; 修改方向键，使其也能够与Ctrl+@组合，选择文本
Right:: {
    if is_need_ignore()
        Send "{Right}"
    else
        forward_char()
    return
}
Left:: {
    if is_need_ignore()
        Send "{Left}"
    else
        backward_char()
    return
}
Up:: {
    if is_need_ignore()
        Send "{Up}"
    else
        previous_line()
    return
}
Down:: {
    if is_need_ignore()
        Send "{Down}"
    else
        next_line()
    return
}

Esc:: {
    Send "{Esc}"
    global is_pre_spc := 0
    return
}

BackSpace:: {
    global
    if is_pre_spc {
        Send "{Backspace}"
        is_pre_spc := 0
    } else {
        Send "{Backspace}"
    }
}